---
title: Build a retrieval agent with LangGraph
sidebarTitle: Agentic RAG
---

:::python

In this tutorial we will build a [retrieval agent](https://python.langchain.com/docs/tutorials/qa_chat_history). Retrieval agents are useful when you want an LLM to make a decision about whether to retrieve context from a vectorstore or respond to the user directly.

By the end of the tutorial we will have done the following:

1. Fetch and preprocess documents that will be used for retrieval.
2. Index those documents for semantic search and create a retriever tool for the agent.
3. Build an agentic RAG system that can decide when to use the retriever tool.

![Hybrid RAG](/images/langgraph-hybrid-rag-tutorial.png)

## Setup

Let's download the required packages and set our API keys:

```python
%%capture --no-stderr
%pip install -U --quiet langgraph "langchain[openai]" langchain-community langchain-text-splitters
```

```python
import getpass
import os


def _set_env(key: str):
    if key not in os.environ:
        os.environ[key] = getpass.getpass(f"{key}:")


_set_env("OPENAI_API_KEY")
```

<Tip>
  Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. [LangSmith](https://docs.smith.langchain.com) lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph.
</Tip>

## 1. Preprocess documents

1. Fetch documents to use in our RAG system. We will use three of the most recent pages from [Lilian Weng's excellent blog](https://lilianweng.github.io/). We'll start by fetching the content of the pages using `WebBaseLoader` utility:
  ```python
  from langchain_community.document_loaders import WebBaseLoader

  urls = [
      "https://lilianweng.github.io/posts/2024-11-28-reward-hacking/",
      "https://lilianweng.github.io/posts/2024-07-07-hallucination/",
      "https://lilianweng.github.io/posts/2024-04-12-diffusion-video/",
  ]

  docs = [WebBaseLoader(url).load() for url in urls]
  ```
  ```python
  docs[0][0].page_content.strip()[:1000]
  ```
2. Split the fetched documents into smaller chunks for indexing into our vectorstore:
  ```python
  from langchain_text_splitters import RecursiveCharacterTextSplitter

  docs_list = [item for sublist in docs for item in sublist]

  text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
      chunk_size=100, chunk_overlap=50
  )
  doc_splits = text_splitter.split_documents(docs_list)
  ```
  ```python
  doc_splits[0].page_content.strip()
  ```

## 2. Create a retriever tool

Now that we have our split documents, we can index them into a vector store that we'll use for semantic search.

1. Use an in-memory vector store and OpenAI embeddings:
  ```python
  from langchain_core.vectorstores import InMemoryVectorStore
  from langchain_openai import OpenAIEmbeddings

  vectorstore = InMemoryVectorStore.from_documents(
      documents=doc_splits, embedding=OpenAIEmbeddings()
  )
  retriever = vectorstore.as_retriever()
  ```
2. Create a retriever tool using LangChain's prebuilt `create_retriever_tool`:
  ```python
  from langchain.tools.retriever import create_retriever_tool

  retriever_tool = create_retriever_tool(
      retriever,
      "retrieve_blog_posts",
      "Search and return information about Lilian Weng blog posts.",
  )
  ```
3. Test the tool:
  ```python
  retriever_tool.invoke({"query": "types of reward hacking"})
  ```

## 3. Generate query

Now we will start building components ([nodes](/oss/langgraph/graph-api#nodes) and [edges](/oss/langgraph/graph-api#edges)) for our agentic RAG graph.

Note that the components will operate on the [`MessagesState`](/oss/langgraph/graph-api#messagesstate) — graph state that contains a `messages` key with a list of [chat messages](https://python.langchain.com/docs/concepts/messages/).

1. Build a `generate_query_or_respond` node. It will call an LLM to generate a response based on the current graph state (list of messages). Given the input messages, it will decide to retrieve using the retriever tool, or respond directly to the user. Note that we're giving the chat model access to the `retriever_tool` we created earlier via `.bind_tools`:
  ```python
  from langgraph.graph import MessagesState
  from langchain.chat_models import init_chat_model

  response_model = init_chat_model("openai:gpt-4.1", temperature=0)


  def generate_query_or_respond(state: MessagesState):
      """Call the model to generate a response based on the current state. Given
      the question, it will decide to retrieve using the retriever tool, or simply respond to the user.
      """
      response = (
          response_model
          .bind_tools([retriever_tool]).invoke(state["messages"])  # [!code highlight]
      )
      return {"messages": [response]}
  ```
2. Try it on a random input:
  ```python
  input = {"messages": [{"role": "user", "content": "hello!"}]}
  generate_query_or_respond(input)["messages"][-1].pretty_print()
  ```
  **Output:**
  ```
  ================================== Ai Message ==================================

  Hello! How can I help you today?
  ```
3. Ask a question that requires semantic search:
  ```python
  input = {
      "messages": [
          {
              "role": "user",
              "content": "What does Lilian Weng say about types of reward hacking?",
          }
      ]
  }
  generate_query_or_respond(input)["messages"][-1].pretty_print()
  ```
  **Output:**
  ```
  ================================== Ai Message ==================================
  Tool Calls:
  retrieve_blog_posts (call_tYQxgfIlnQUDMdtAhdbXNwIM)
  Call ID: call_tYQxgfIlnQUDMdtAhdbXNwIM
  Args:
      query: types of reward hacking
  ```

## 4. Grade documents

1. Add a [conditional edge](/oss/langgraph/graph-api#conditional-edges) — `grade_documents` — to determine whether the retrieved documents are relevant to the question. We will use a model with a structured output schema `GradeDocuments` for document grading. The `grade_documents` function will return the name of the node to go to based on the grading decision (`generate_answer` or `rewrite_question`):
  ```python
  from pydantic import BaseModel, Field
  from typing import Literal

  GRADE_PROMPT = (
      "You are a grader assessing relevance of a retrieved document to a user question. \n "
      "Here is the retrieved document: \n\n {context} \n\n"
      "Here is the user question: {question} \n"
      "If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \n"
      "Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question."
  )


  class GradeDocuments(BaseModel):  # [!code highlight]
      """Grade documents using a binary score for relevance check."""

      binary_score: str = Field(
          description="Relevance score: 'yes' if relevant, or 'no' if not relevant"
      )


  grader_model = init_chat_model("openai:gpt-4.1", temperature=0)


  def grade_documents(
      state: MessagesState,
  ) -> Literal["generate_answer", "rewrite_question"]:
      """Determine whether the retrieved documents are relevant to the question."""
      question = state["messages"][0].content
      context = state["messages"][-1].content

      prompt = GRADE_PROMPT.format(question=question, context=context)
      response = (
          grader_model
          .with_structured_output(GradeDocuments).invoke(  # [!code highlight]
              [{"role": "user", "content": prompt}]
          )
      )
      score = response.binary_score

      if score == "yes":
          return "generate_answer"
      else:
          return "rewrite_question"
  ```
2. Run this with irrelevant documents in the tool response:
  ```python
  from langchain_core.messages import convert_to_messages

  input = {
      "messages": convert_to_messages(
          [
              {
                  "role": "user",
                  "content": "What does Lilian Weng say about types of reward hacking?",
              },
              {
                  "role": "assistant",
                  "content": "",
                  "tool_calls": [
                      {
                          "id": "1",
                          "name": "retrieve_blog_posts",
                          "args": {"query": "types of reward hacking"},
                      }
                  ],
              },
              {"role": "tool", "content": "meow", "tool_call_id": "1"},
          ]
      )
  }
  grade_documents(input)
  ```
3. Confirm that the relevant documents are classified as such:
  ```python
  input = {
      "messages": convert_to_messages(
          [
              {
                  "role": "user",
                  "content": "What does Lilian Weng say about types of reward hacking?",
              },
              {
                  "role": "assistant",
                  "content": "",
                  "tool_calls": [
                      {
                          "id": "1",
                          "name": "retrieve_blog_posts",
                          "args": {"query": "types of reward hacking"},
                      }
                  ],
              },
              {
                  "role": "tool",
                  "content": "reward hacking can be categorized into two types: environment or goal misspecification, and reward tampering",
                  "tool_call_id": "1",
              },
          ]
      )
  }
  grade_documents(input)
  ```

## 5. Rewrite question

1. Build the `rewrite_question` node. The retriever tool can return potentially irrelevant documents, which indicates a need to improve the original user question. To do so, we will call the `rewrite_question` node:
  ```python
  REWRITE_PROMPT = (
      "Look at the input and try to reason about the underlying semantic intent / meaning.\n"
      "Here is the initial question:"
      "\n ------- \n"
      "{question}"
      "\n ------- \n"
      "Formulate an improved question:"
  )


  def rewrite_question(state: MessagesState):
      """Rewrite the original user question."""
      messages = state["messages"]
      question = messages[0].content
      prompt = REWRITE_PROMPT.format(question=question)
      response = response_model.invoke([{"role": "user", "content": prompt}])
      return {"messages": [{"role": "user", "content": response.content}]}
  ```
2. Try it out:
  ```python
  input = {
      "messages": convert_to_messages(
          [
              {
                  "role": "user",
                  "content": "What does Lilian Weng say about types of reward hacking?",
              },
              {
                  "role": "assistant",
                  "content": "",
                  "tool_calls": [
                      {
                          "id": "1",
                          "name": "retrieve_blog_posts",
                          "args": {"query": "types of reward hacking"},
                      }
                  ],
              },
              {"role": "tool", "content": "meow", "tool_call_id": "1"},
          ]
      )
  }

  response = rewrite_question(input)
  print(response["messages"][-1]["content"])
  ```
  **Output:**
  ```
  What are the different types of reward hacking described by Lilian Weng, and how does she explain them?
  ```

## 6. Generate an answer

1. Build `generate_answer` node: if we pass the grader checks, we can generate the final answer based on the original question and the retrieved context:
  ```python
  GENERATE_PROMPT = (
      "You are an assistant for question-answering tasks. "
      "Use the following pieces of retrieved context to answer the question. "
      "If you don't know the answer, just say that you don't know. "
      "Use three sentences maximum and keep the answer concise.\n"
      "Question: {question} \n"
      "Context: {context}"
  )


  def generate_answer(state: MessagesState):
      """Generate an answer."""
      question = state["messages"][0].content
      context = state["messages"][-1].content
      prompt = GENERATE_PROMPT.format(question=question, context=context)
      response = response_model.invoke([{"role": "user", "content": prompt}])
      return {"messages": [response]}
  ```
2. Try it:
  ```python
  input = {
      "messages": convert_to_messages(
          [
              {
                  "role": "user",
                  "content": "What does Lilian Weng say about types of reward hacking?",
              },
              {
                  "role": "assistant",
                  "content": "",
                  "tool_calls": [
                      {
                          "id": "1",
                          "name": "retrieve_blog_posts",
                          "args": {"query": "types of reward hacking"},
                      }
                  ],
              },
              {
                  "role": "tool",
                  "content": "reward hacking can be categorized into two types: environment or goal misspecification, and reward tampering",
                  "tool_call_id": "1",
              },
          ]
      )
  }

  response = generate_answer(input)
  response["messages"][-1].pretty_print()
  ```
  **Output:**
  ```
  ================================== Ai Message ==================================

  Lilian Weng categorizes reward hacking into two types: environment or goal misspecification, and reward tampering. She considers reward hacking as a broad concept that includes both of these categories. Reward hacking occurs when an agent exploits flaws or ambiguities in the reward function to achieve high rewards without performing the intended behaviors.
  ```

## 7. Assemble the graph

* Start with a `generate_query_or_respond` and determine if we need to call `retriever_tool`
* Route to next step using `tools_condition`:
  * If `generate_query_or_respond` returned `tool_calls`, call `retriever_tool` to retrieve context
  * Otherwise, respond directly to the user
* Grade retrieved document content for relevance to the question (`grade_documents`) and route to next step:
  * If not relevant, rewrite the question using `rewrite_question` and then call `generate_query_or_respond` again
  * If relevant, proceed to `generate_answer` and generate final response using the `ToolMessage` with the retrieved document context

```python
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langgraph.prebuilt import tools_condition

workflow = StateGraph(MessagesState)

# Define the nodes we will cycle between
workflow.add_node(generate_query_or_respond)
workflow.add_node("retrieve", ToolNode([retriever_tool]))
workflow.add_node(rewrite_question)
workflow.add_node(generate_answer)

workflow.add_edge(START, "generate_query_or_respond")

# Decide whether to retrieve
workflow.add_conditional_edges(
    "generate_query_or_respond",
    # Assess LLM decision (call `retriever_tool` tool or respond to the user)
    tools_condition,
    {
        # Translate the condition outputs to nodes in our graph
        "tools": "retrieve",
        END: END,
    },
)

# Edges taken after the `action` node is called.
workflow.add_conditional_edges(
    "retrieve",
    # Assess agent decision
    grade_documents,
)
workflow.add_edge("generate_answer", END)
workflow.add_edge("rewrite_question", "generate_query_or_respond")

# Compile
graph = workflow.compile()
```

Visualize the graph:

```python
from IPython.display import Image, display

display(Image(graph.get_graph().draw_mermaid_png()))
```

![Graph](/images/agentic-rag-output.png)

## 8. Run the agentic RAG

```python
for chunk in graph.stream(
    {
        "messages": [
            {
                "role": "user",
                "content": "What does Lilian Weng say about types of reward hacking?",
            }
        ]
    }
):
    for node, update in chunk.items():
        print("Update from node", node)
        update["messages"][-1].pretty_print()
        print("\n\n")
```

**Output:**

```
Update from node generate_query_or_respond
================================== Ai Message ==================================
Tool Calls:
  retrieve_blog_posts (call_NYu2vq4km9nNNEFqJwefWKu1)
 Call ID: call_NYu2vq4km9nNNEFqJwefWKu1
  Args:
    query: types of reward hacking



Update from node retrieve
================================= Tool Message ==================================
Name: retrieve_blog_posts

(Note: Some work defines reward tampering as a distinct category of misalignment behavior from reward hacking. But I consider reward hacking as a broader concept here.)
At a high level, reward hacking can be categorized into two types: environment or goal misspecification, and reward tampering.

Why does Reward Hacking Exist?#

Pan et al. (2022) investigated reward hacking as a function of agent capabilities, including (1) model size, (2) action space resolution, (3) observation space noise, and (4) training time. They also proposed a taxonomy of three types of misspecified proxy rewards:

Let's Define Reward Hacking#
Reward shaping in RL is challenging. Reward hacking occurs when an RL agent exploits flaws or ambiguities in the reward function to obtain high rewards without genuinely learning the intended behaviors or completing the task as designed. In recent years, several related concepts have been proposed, all referring to some form of reward hacking:



Update from node generate_answer
================================== Ai Message ==================================

Lilian Weng categorizes reward hacking into two types: environment or goal misspecification, and reward tampering. She considers reward hacking as a broad concept that includes both of these categories. Reward hacking occurs when an agent exploits flaws or ambiguities in the reward function to achieve high rewards without performing the intended behaviors.
```


:::

:::js
We can implement
[Retrieval Agents](https://js.langchain.com/docs/use_cases/question_answering/conversational_retrieval_agents)
in [LangGraph](https://js.langchain.com/docs/langgraph).

## Setup

### Load env vars

Add a `.env` variable in the root of the `./examples` folder with your
variables.

```typescript
// import dotenv from 'dotenv';

// dotenv.config();
```

### Install dependencies

```bash
npm install cheerio zod zod-to-json-schema langchain @langchain/openai @langchain/core @langchain/community @langchain/textsplitters
```

## Retriever

```typescript
import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio";
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { OpenAIEmbeddings } from "@langchain/openai";

const urls = [
  "https://lilianweng.github.io/posts/2023-06-23-agent/",
  "https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/",
  "https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/",
];

const docs = await Promise.all(
  urls.map((url) => new CheerioWebBaseLoader(url).load()),
);
const docsList = docs.flat();

const textSplitter = new RecursiveCharacterTextSplitter({
  chunkSize: 500,
  chunkOverlap: 50,
});
const docSplits = await textSplitter.splitDocuments(docsList);

// Add to vectorDB
const vectorStore = await MemoryVectorStore.fromDocuments(
  docSplits,
  new OpenAIEmbeddings(),
);

const retriever = vectorStore.asRetriever();
```

## Agent state

We will define a graph.

You may pass a custom `state` object to the graph, or use a simple list of
`messages`.

Our state will be a list of `messages`.

Each node in our graph will append to it.

```typescript
import { Annotation } from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";

const GraphState = Annotation.Root({
  messages: Annotation<BaseMessage[]>({
    reducer: (x, y) => x.concat(y),
    default: () => [],
  })
})
```

```typescript
import { createRetrieverTool } from "langchain/tools/retriever";
import { ToolNode } from "@langchain/langgraph/prebuilt";

const tool = createRetrieverTool(
  retriever,
  {
    name: "retrieve_blog_posts",
    description:
      "Search and return information about Lilian Weng blog posts on LLM agents, prompt engineering, and adversarial attacks on LLMs.",
  },
);
const tools = [tool];

const toolNode = new ToolNode<typeof GraphState.State>(tools);
```

## Nodes and Edges

Each node will -

1/ Either be a function or a runnable.

2/ Modify the `state`.

The edges choose which node to call next.

We can lay out an agentic RAG graph like this:

![Hybrid RAG](/images/langgraph-hybrid-rag-tutorial.png)

### Edges

```typescript
import { END } from "@langchain/langgraph";
import { pull } from "langchain/hub";
import { z } from "zod";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { AIMessage, BaseMessage } from "@langchain/core/messages";

/**
 * Decides whether the agent should retrieve more information or end the process.
 * This function checks the last message in the state for a function call. If a tool call is
 * present, the process continues to retrieve information. Otherwise, it ends the process.
 * @param {typeof GraphState.State} state - The current state of the agent, including all messages.
 * @returns {string} - A decision to either "continue" the retrieval process or "end" it.
 */
function shouldRetrieve(state: typeof GraphState.State): string {
  const { messages } = state;
  console.log("---DECIDE TO RETRIEVE---");
  const lastMessage = messages[messages.length - 1];

  if ("tool_calls" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls.length) {
    console.log("---DECISION: RETRIEVE---");
    return "retrieve";
  }
  // If there are no tool calls then we finish.
  return END;
}

/**
 * Determines whether the Agent should continue based on the relevance of retrieved documents.
 * This function checks if the last message in the conversation is of type FunctionMessage, indicating
 * that document retrieval has been performed. It then evaluates the relevance of these documents to the user's
 * initial question using a predefined model and output parser. If the documents are relevant, the conversation
 * is considered complete. Otherwise, the retrieval process is continued.
 * @param {typeof GraphState.State} state - The current state of the agent, including all messages.
 * @returns {Promise<Partial<typeof GraphState.State>>} - The updated state with the new message added to the list of messages.
 */
async function gradeDocuments(state: typeof GraphState.State): Promise<Partial<typeof GraphState.State>> {
  console.log("---GET RELEVANCE---");

  const { messages } = state;
  const tool = {
    name: "give_relevance_score",
    description: "Give a relevance score to the retrieved documents.",
    schema: z.object({
      binaryScore: z.string().describe("Relevance score 'yes' or 'no'"),
    })
  }

  const prompt = ChatPromptTemplate.fromTemplate(
    `You are a grader assessing relevance of retrieved docs to a user question.
  Here are the retrieved docs:
  \n ------- \n
  {context}
  \n ------- \n
  Here is the user question: {question}
  If the content of the docs are relevant to the users question, score them as relevant.
  Give a binary score 'yes' or 'no' score to indicate whether the docs are relevant to the question.
  Yes: The docs are relevant to the question.
  No: The docs are not relevant to the question.`,
  );

  const model = new ChatOpenAI({
    model: "gpt-4o",
    temperature: 0,
  }).bindTools([tool], {
    tool_choice: tool.name,
  });

  const chain = prompt.pipe(model);

  const lastMessage = messages[messages.length - 1];

  const score = await chain.invoke({
    question: messages[0].content as string,
    context: lastMessage.content as string,
  });

  return {
    messages: [score]
  };
}

/**
 * Check the relevance of the previous LLM tool call.
 *
 * @param {typeof GraphState.State} state - The current state of the agent, including all messages.
 * @returns {string} - A directive to either "yes" or "no" based on the relevance of the documents.
 */
function checkRelevance(state: typeof GraphState.State): string {
  console.log("---CHECK RELEVANCE---");

  const { messages } = state;
  const lastMessage = messages[messages.length - 1];
  if (!("tool_calls" in lastMessage)) {
    throw new Error("The 'checkRelevance' node requires the most recent message to contain tool calls.")
  }
  const toolCalls = (lastMessage as AIMessage).tool_calls;
  if (!toolCalls || !toolCalls.length) {
    throw new Error("Last message was not a function message");
  }

  if (toolCalls[0].args.binaryScore === "yes") {
    console.log("---DECISION: DOCS RELEVANT---");
    return "yes";
  }
  console.log("---DECISION: DOCS NOT RELEVANT---");
  return "no";
}

// Nodes

/**
 * Invokes the agent model to generate a response based on the current state.
 * This function calls the agent model to generate a response to the current conversation state.
 * The response is added to the state's messages.
 * @param {typeof GraphState.State} state - The current state of the agent, including all messages.
 * @returns {Promise<Partial<typeof GraphState.State>>} - The updated state with the new message added to the list of messages.
 */
async function agent(state: typeof GraphState.State): Promise<Partial<typeof GraphState.State>> {
  console.log("---CALL AGENT---");

  const { messages } = state;
  // Find the AIMessage which contains the `give_relevance_score` tool call,
  // and remove it if it exists. This is because the agent does not need to know
  // the relevance score.
  const filteredMessages = messages.filter((message) => {
    if ("tool_calls" in message && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
      return message.tool_calls[0].name !== "give_relevance_score";
    }
    return true;
  });

  const model = new ChatOpenAI({
    model: "gpt-4o",
    temperature: 0,
    streaming: true,
  }).bindTools(tools);

  const response = await model.invoke(filteredMessages);
  return {
    messages: [response],
  };
}

/**
 * Transform the query to produce a better question.
 * @param {typeof GraphState.State} state - The current state of the agent, including all messages.
 * @returns {Promise<Partial<typeof GraphState.State>>} - The updated state with the new message added to the list of messages.
 */
async function rewrite(state: typeof GraphState.State): Promise<Partial<typeof GraphState.State>> {
  console.log("---TRANSFORM QUERY---");

  const { messages } = state;
  const question = messages[0].content as string;
  const prompt = ChatPromptTemplate.fromTemplate(
    `Look at the input and try to reason about the underlying semantic intent / meaning. \n
Here is the initial question:
\n ------- \n
{question}
\n ------- \n
Formulate an improved question:`,
  );

  // Grader
  const model = new ChatOpenAI({
    model: "gpt-4o",
    temperature: 0,
    streaming: true,
  });
  const response = await prompt.pipe(model).invoke({ question });
  return {
    messages: [response],
  };
}

/**
 * Generate answer
 * @param {typeof GraphState.State} state - The current state of the agent, including all messages.
 * @returns {Promise<Partial<typeof GraphState.State>>} - The updated state with the new message added to the list of messages.
 */
async function generate(state: typeof GraphState.State): Promise<Partial<typeof GraphState.State>> {
  console.log("---GENERATE---");

  const { messages } = state;
  const question = messages[0].content as string;
  // Extract the most recent ToolMessage
  const lastToolMessage = messages.slice().reverse().find((msg) => msg._getType() === "tool");
  if (!lastToolMessage) {
    throw new Error("No tool message found in the conversation history");
  }

  const docs = lastToolMessage.content as string;

  const prompt = await pull<ChatPromptTemplate>("rlm/rag-prompt");

  const llm = new ChatOpenAI({
    model: "gpt-4o",
    temperature: 0,
    streaming: true,
  });

  const ragChain = prompt.pipe(llm);

  const response = await ragChain.invoke({
    context: docs,
    question,
  });

  return {
    messages: [response],
  };
}
```

## Graph

* Start with an agent, `callModel`
* Agent make a decision to call a function
* If so, then `action` to call tool (retriever)
* Then call agent with the tool output added to messages (`state`)

```typescript
import { StateGraph } from "@langchain/langgraph";

// Define the graph
const workflow = new StateGraph(GraphState)
  // Define the nodes which we'll cycle between.
  .addNode("agent", agent)
  .addNode("retrieve", toolNode)
  .addNode("gradeDocuments", gradeDocuments)
  .addNode("rewrite", rewrite)
  .addNode("generate", generate);
```

```typescript
import { START } from "@langchain/langgraph";

// Call agent node to decide to retrieve or not
workflow.addEdge(START, "agent");

// Decide whether to retrieve
workflow.addConditionalEdges(
  "agent",
  // Assess agent decision
  shouldRetrieve,
);

workflow.addEdge("retrieve", "gradeDocuments");

// Edges taken after the `action` node is called.
workflow.addConditionalEdges(
  "gradeDocuments",
  // Assess agent decision
  checkRelevance,
  {
    // Call tool node
    yes: "generate",
    no: "rewrite", // placeholder
  },
);

workflow.addEdge("generate", END);
workflow.addEdge("rewrite", "agent");

// Compile
const app = workflow.compile();
```

```typescript
import { HumanMessage } from "@langchain/core/messages";

const inputs = {
  messages: [
    new HumanMessage(
      "What are the types of agent memory based on Lilian Weng's blog post?",
    ),
  ],
};
let finalState;
for await (const output of await app.stream(inputs)) {
  for (const [key, value] of Object.entries(output)) {
    const lastMsg = output[key].messages[output[key].messages.length - 1];
    console.log(`Output from node: '${key}'`);
    console.dir({
      type: lastMsg._getType(),
      content: lastMsg.content,
      tool_calls: lastMsg.tool_calls,
    }, { depth: null });
    console.log("---\n");
    finalState = value;
  }
}

console.log(JSON.stringify(finalState, null, 2));
```

```output
---CALL AGENT---
---DECIDE TO RETRIEVE---
---DECISION: RETRIEVE---
Output from node: 'agent'
{
  type: 'ai',
  content: '',
  tool_calls: [
    {
      name: 'retrieve_blog_posts',
      args: { query: 'types of agent memory' },
      id: 'call_adLYkV7T2ry1EZFboT0jPuwn',
      type: 'tool_call'
    }
  ]
}
---

Output from node: 'retrieve'
{
  type: 'tool',
  content: 'Agent System Overview\n' +
    '                \n' +
    '                    Component One: Planning\n' +
    '                        \n' +
    '                \n' +
    '                    Task Decomposition\n' +
    '                \n' +
    '                    Self-Reflection\n' +
    '                \n' +
    '                \n' +
    '                    Component Two: Memory\n' +
    '                        \n' +
    '                \n' +
    '                    Types of Memory\n' +
    '                \n' +
    '                    Maximum Inner Product Search (MIPS)\n' +
    '\n' +
    'Memory stream: is a long-term memory module (external database) that records a comprehensive list of agents’ experience in natural language.\n' +
    '\n' +
    'Each element is an observation, an event directly provided by the agent.\n' +
    '- Inter-agent communication can trigger new natural language statements.\n' +
    '\n' +
    '\n' +
    'Retrieval model: surfaces the context to inform the agent’s behavior, according to relevance, recency and importance.\n' +
    '\n' +
    'Planning\n' +
    '\n' +
    'Subgoal and decomposition: The agent breaks down large tasks into smaller, manageable subgoals, enabling efficient handling of complex tasks.\n' +
    'Reflection and refinement: The agent can do self-criticism and self-reflection over past actions, learn from mistakes and refine them for future steps, thereby improving the quality of final results.\n' +
    '\n' +
    '\n' +
    'Memory\n' +
    '\n' +
    'The design of generative agents combines LLM with memory, planning and reflection mechanisms to enable agents to behave conditioned on past experience, as well as to interact with other agents.',
  tool_calls: undefined
}
---

---GET RELEVANCE---
---CHECK RELEVANCE---
---DECISION: DOCS NOT RELEVANT---
Output from node: 'gradeDocuments'
{
  type: 'ai',
  content: '',
  tool_calls: [
    {
      name: 'give_relevance_score',
      args: { binaryScore: 'no' },
      type: 'tool_call',
      id: 'call_AGE7gORVFubExfJWcjb0C2nV'
    }
  ]
}
---

---TRANSFORM QUERY---
Output from node: 'rewrite'
{
  type: 'ai',
  content: "What are the different types of agent memory described in Lilian Weng's blog post?",
  tool_calls: []
}
---

---CALL AGENT---
---DECIDE TO RETRIEVE---
Output from node: 'agent'
{
  type: 'ai',
  content: "Lilian Weng's blog post describes the following types of agent memory:\n" +
    '\n' +
    '1. **Memory Stream**:\n' +
    '   - This is a long-term memory module (external database) that records a comprehensive list of agents’ experiences in natural language.\n' +
    '   - Each element in the memory stream is an observation or an event directly provided by the agent.\n' +
    '   - Inter-agent communication can trigger new natural language statements to be added to the memory stream.\n' +
    '\n' +
    '2. **Retrieval Model**:\n' +
    '   - This model surfaces the context to inform the agent’s behavior based on relevance, recency, and importance.\n' +
    '\n' +
    'These memory types are part of a broader design that combines generative agents with memory, planning, and reflection mechanisms to enable agents to behave based on past experiences and interact with other agents.',
  tool_calls: []
}
---

{
  "messages": [
    {
      "lc": 1,
      "type": "constructor",
      "id": [
        "langchain_core",
        "messages",
        "AIMessageChunk"
      ],
      "kwargs": {
        "content": "Lilian Weng's blog post describes the following types of agent memory:\n\n1. **Memory Stream**:\n   - This is a long-term memory module (external database) that records a comprehensive list of agents’ experiences in natural language.\n   - Each element in the memory stream is an observation or an event directly provided by the agent.\n   - Inter-agent communication can trigger new natural language statements to be added to the memory stream.\n\n2. **Retrieval Model**:\n   - This model surfaces the context to inform the agent’s behavior based on relevance, recency, and importance.\n\nThese memory types are part of a broader design that combines generative agents with memory, planning, and reflection mechanisms to enable agents to behave based on past experiences and interact with other agents.",
        "additional_kwargs": {},
        "response_metadata": {
          "estimatedTokenUsage": {
            "promptTokens": 280,
            "completionTokens": 155,
            "totalTokens": 435
          },
          "prompt": 0,
          "completion": 0,
          "finish_reason": "stop",
          "system_fingerprint": "fp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3bfp_3cd8b62c3b"
        },
        "tool_call_chunks": [],
        "id": "chatcmpl-9zAaVQGmTLiCaFvtbxUK60qMFsSmU",
        "usage_metadata": {
          "input_tokens": 363,
          "output_tokens": 156,
          "total_tokens": 519
        },
        "tool_calls": [],
        "invalid_tool_calls": []
      }
    }
  ]
}
```

:::
